/*
 * Copyright 2015 CPE Lyon - IRC 2013/2016 - Cours de programmation réseau - Groupe H
 *
 * This file is part of BOTT.
 *
 * BOTT is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * BOTT is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with BOTT. If not, see http://www.gnu.org/licenses/.
 */

/*
 * Fonctions dédiées au serveur de messagerie.
 */

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <fcntl.h>
#include <pthread.h>
#include "protocole.h"
#include "serveur.h"
#include "fonctions.h"
#include "serveur.h"
#include "fonctions-serveur.h"
#include "traitements-serveur.h"

/* Traitement d'un message reçu */
void *messageTreatment(void* transmitData) {
    threadData data = (*(threadData *)transmitData);
    int socketDesc = data.socketDesc;
    bimp_msg readData = data.receivedData;
    client *allClients = data.listClients;
    chat *allChats = data.listChats;
    int idCommand = readData.idCommand;
    int idUser = readData.idUser;

    /* On vérifie que la commande est correcte */
    if (checkClientCommand(idCommand)) {
        bimp_msg sendData;
        switch (idCommand) {
            case 1:
                sendData = treatConnect(readData, allClients, data.clientAddr, &idUser);
                break;
            case 2:
                sendData = treatJoin(readData, allClients, allChats);
                break;
            case 3:
                sendData = treatSay(readData, allClients, allChats, socketDesc);
                break;
            case 4:
                sendData = treatLeave(readData, allClients, allChats);
                break;
            case 5:
                sendData = treatDisconnect(readData, allClients, allChats);
                break;
            case 6:
                sendData = treatAlive(readData.idUser);
                break;
            case 7:
                sendData = treatNick(readData, allClients);
                break;
            case 8:
                sendData = treatGetNick(readData, allClients);
                break;
        }

        /* Envoyer l'éventuelle réponse (sauf en cas d'erreur résultant de la non-connexion du client) */
        if (idUser != 0 && sendData.idCommand != 0 && sendData.idCommand != 104) {
            struct sockaddr_in sendAddr = getSendStruct(getUserIp(allClients, idUser), getUserPort(allClients, idUser));
            if (sendto(socketDesc, &sendData, sizeof sendData , 0, (struct sockaddr *) &sendAddr, sizeof(sendAddr)) == -1) {
                perror("Socket send error");
            }
        } else {
            if (sendData.idCommand != 0) {
                if (sendData.idCommand == 110 || sendData.idCommand == 104) {
                    if (sendto(socketDesc, &sendData, sizeof sendData , 0, (struct sockaddr *) &data.clientAddr, sizeof(data.clientAddr)) == -1) {
                        perror("Socket send error");
                    }
                } else {
                    if (isDebugActivated()) {
                        fprintf(stderr, "Incorrect ID\n");
                    }
                }
            }
        }
    } else {
        if (isDebugActivated()) {
            fprintf(stderr, "Incorrect command\n");
        }
    }
    pthread_exit(NULL);
}

/* Traiter les demandes de connexion */
bimp_msg treatConnect(bimp_msg receivedData, client *allClients, struct sockaddr_in clientAddr, int *idUser) {
    int id =  getNextClientId(allClients);
    *idUser = id;
    bimp_msg sendData;
    if (id != 0) {
        /* Créer la structure de l'ACKCONNECT */
        sendData.idCommand = 105;
        sendData.idUser = id;
        sendData.idChat = 0;
        strcpy(sendData.data, "");
        /* Création et initialisation du client */
        client newClient;
        newClient.idUser = id;
        newClient.ipUser = clientAddr.sin_addr;
        newClient.portUser = clientAddr.sin_port;
        strcpy(newClient.nickname, receivedData.data);
        for (int i = 0; i < MAX; i++) {
            newClient.listChats[i] = 0;
        }
        /* Ajout du client dans la liste */
        for (int i = 0; i < getMaxClientsNumber() * getMaxChatsNumber(); i++) {
            if (allClients[i].idUser == 0) {
                allClients[i] = newClient;
                break;
            }
        }
        if (isDebugActivated()) {
            printf("[ACKCONNECT] User %s with ID %d\n", receivedData.data, id);
        }
    } else {
        /* Créer la structure du NACKCONNECT */
        sendData.idCommand = 110;
        sendData.idUser = 0;
        sendData.idChat = 0;
        strcpy(sendData.data, "Maximum client number reached");
        if (isDebugActivated()) {
            printf("[NACKCONNECT] User %s\n", receivedData.data);
        }
    }
    return sendData;
}

/* Traiter les demandes d'accès à un salon */
bimp_msg treatJoin(bimp_msg receivedData, client *allClients, chat *allChats) {
    bimp_msg sendData;
    if (isClientConnected(allClients, receivedData.idUser)) {
        int index = getChatIndex(allChats, receivedData.data);
        if (index == -1) {
            int chatIndex = getNextChatIndex(allChats);
            if (chatIndex == -1) {
                /* Créer la structure du ERROR */
                sendData.idCommand = 104;
                sendData.idUser = 0;
                sendData.idChat = 0;
                strcpy(sendData.data, "Maximum chat number reached");
                if (isDebugActivated()) {
                    printf("[ERROR] Maximum chat number reached\n");
                }
            } else {
                int chatId = getNextChatId(allChats);
                /* Création et initialisation du salon */
                chat newChat;
                newChat.idChat = chatId;
                strcpy(newChat.chatName, receivedData.data);
                for (int i = 0; i < getMaxClientsNumber(); i++) {
                    newChat.listClients[i] = 0;
                }
                newChat.listClients[0] = receivedData.idUser;
                allChats[chatIndex] = newChat;
                /* Ajout du salon dans le client */
                addChatInClient(receivedData.idUser, chatId, allClients);
                /* Créer la structure du ACKJOIN */
                sendData.idCommand = 106;
                sendData.idUser = 0;
                sendData.idChat = allChats[chatIndex].idChat;
                strcpy(sendData.data, "");
                if (isDebugActivated()) {
                    printf("[ACKJOIN] User %d in chat %s\n", receivedData.idUser, receivedData.data);
                }
            }
        } else {
            /* Le salon existe déjà */
            if (isClientInChat(allChats, receivedData.idUser, allChats[index].idChat)) {
                /* Créer la structure du ERROR */
                sendData.idCommand = 104;
                sendData.idUser = 0;
                sendData.idChat = 0;
                strcpy(sendData.data, "Client already connected to chat");
                if (isDebugActivated()) {
                    printf("[ERROR] Client already connected to chat\n");
                }
            } else {
                int full = addClientInChat(receivedData.idUser, allChats[index].idChat, allChats);
                if (!full) {
                    addChatInClient(receivedData.idUser, allChats[index].idChat, allClients);
                    /* Créer la structure du ACKJOIN */
                    sendData.idCommand = 106;
                    sendData.idUser = 0;
                    sendData.idChat = allChats[index].idChat;
                    strcpy(sendData.data, "");
                    if (isDebugActivated()) {
                        printf("[ACKJOIN] User %d in chat %s\n", receivedData.idUser, receivedData.data);
                    }
                } else {
                    /* Créer la structure du NACKJOIN */
                    sendData.idCommand = 111;
                    sendData.idUser = 0;
                    sendData.idChat = 0;
                    strcpy(sendData.data, "Maximum number of clients in chat reached");
                    if (isDebugActivated()) {
                        printf("[NACKJOIN] User %d in chat %s\n", receivedData.idUser, receivedData.data);
                    }
                }
            }
        }
    } else {
        /* Créer la structure du ERROR */
        sendData.idCommand = 104;
        sendData.idUser = 0;
        sendData.idChat = 0;
        strcpy(sendData.data, "Client not connected");
        if (isDebugActivated()) {
            printf("[ERROR] Client not connected\n");
        }
    }
    return sendData;
}

/* Traiter un message envoyé par un client sur un salon */
bimp_msg treatSay(bimp_msg receivedData, client *allClients, chat *allChats, int socketDesc) {
    bimp_msg sendData;
    if (isClientConnected(allClients, receivedData.idUser) && isClientInChat(allChats, receivedData.idUser, receivedData.idChat)) {
        int index = getChatIndexFromId(allChats, receivedData.idChat);
        if (index == -1) {
            /* Créer la structure du ERROR */
            sendData.idCommand = 104;
            sendData.idUser = 0;
            sendData.idChat = 0;
            strcpy(sendData.data, "Chat not exists");
            if (isDebugActivated()) {
                printf("[ERROR] Chat not exists\n");
            }
        } else {
            int idClient;
            /* Envoyer le message à chaque client connecté au serveur et au salon*/
            for (int i = 0; i < MAX; i++) {
                if (allChats[index].listClients[i] != receivedData.idUser && allChats[index].listClients[i] != 0) {
                    idClient = allChats[index].listClients[i];
                    struct sockaddr_in sendAddr = getSendStruct(getUserIp(allClients, idClient), getUserPort(allClients, idClient));
                    sendData.idCommand = 102;
                    sendData.idUser = receivedData.idUser;
                    sendData.idChat = receivedData.idChat;
                    strcpy(sendData.data, receivedData.data);
                    if (isDebugActivated()) {
                        printf("[TELL] User %d in chat %s from user %d: %s\n", sendData.idUser, allChats[index].chatName, receivedData.idUser, receivedData.data);
                    }
                    if (sendto(socketDesc, &sendData, sizeof sendData , 0, (struct sockaddr *) &sendAddr, sizeof(sendAddr)) == -1) {
                        perror("Socket send error");
                    }
                }
            }
            /* Créer la structure vide */
            sendData.idCommand = 0;
            sendData.idUser = 0;
            sendData.idChat = 0;
            strcpy(sendData.data, "");
        }
    } else {
        /* Créer la structure du ERROR */
        sendData.idCommand = 104;
        sendData.idUser = 0;
        sendData.idChat = 0;
        strcpy(sendData.data, "Client not connected or not in chat");
        if (isDebugActivated()) {
            printf("[ERROR] Client not connected or not in chat\n");
        }
    }
    return sendData;
}

/* Traiter les demandes de déconnexion à un salon */
bimp_msg treatLeave(bimp_msg receivedData, client *allClients, chat *allChats) {
    bimp_msg sendData;
    if (isClientConnected(allClients, receivedData.idUser) && isClientInChat(allChats, receivedData.idUser, receivedData.idChat)) {
        int index = getChatIndexFromId(allChats, receivedData.idChat);
        if (index == -1) {
            /* Créer la structure du ERROR */
            sendData.idCommand = 104;
            sendData.idUser = 0;
            sendData.idChat = 0;
            strcpy(sendData.data, "Chat not exists");
            if (isDebugActivated()) {
                printf("[ERROR] Chat not exists\n");
            }
        } else {
            /* Suppression du client dans la liste du salon */
            deleteClientInChat(receivedData.idUser, receivedData.idChat, allChats);
            deleteChatInClient(receivedData.idUser, receivedData.idChat, allClients);
            int empty = 0;
            for (int i = 0; i < getMaxClientsNumber(); i++) {
                if (allChats[index].listClients[i] != 0) {
                    empty = 1;
                }
            }
            if (empty) {
                chat emptyChat;
                emptyChat.idChat = 0;
                allChats[index] = emptyChat;
            }
            /* Créer la structure du ACKLEAVE */
            sendData.idCommand = 107;
            sendData.idUser = 0;
            sendData.idChat = receivedData.idChat;
            strcpy(sendData.data, "");
            if (isDebugActivated()) {
                printf("[ACKLEAVE] User %d in chat %d\n", receivedData.idUser, receivedData.idChat);
            }
        }
    } else {
        /* Créer la structure du ERROR */
        sendData.idCommand = 104;
        sendData.idUser = 0;
        sendData.idChat = 0;
        strcpy(sendData.data, "Client not connected or has not joined chat");
        if (isDebugActivated()) {
            printf("[ERROR] Client not connected or has not joined chat\n");
        }
    }
    return sendData;
}

/* Traiter les demandes de déconnexion */
bimp_msg treatDisconnect(bimp_msg receivedData, client *allClients, chat *allChats) {
    bimp_msg sendData;
    if (isClientConnected(allClients, receivedData.idUser)) {
        int index = getUserIndexFromId(allClients, receivedData.idUser);
        /* Déconnexion du client de tous les salons */
        int idChat;
        int indexChat;
        for (int i = 0; i < MAX; i++) {
            idChat = allClients[index].listChats[i];
            indexChat = getChatIndexFromId(allChats, idChat);
            for (int j = 0; j < getMaxClientsNumber(); j++) {
                if (allChats[indexChat].listClients[j] == receivedData.idUser) {
                    allChats[indexChat].listClients[j] = 0;
                }
            }
        }
        /* Suppression du client */
        client emptyClient;
        emptyClient.idUser = 0;
        allClients[index] = emptyClient;
        if (isDebugActivated()) {
            printf("[DISCONNECT] Client %d is disconnected\n", receivedData.idUser);
        }
    }
    /* Créer la structure vide */
    sendData.idCommand = 0;
    sendData.idUser = 0;
    sendData.idChat = 0;
    strcpy(sendData.data, "");
    return sendData;
}

/* Traiter les demandes de maintien de la connexion */
bimp_msg treatAlive(int idUser) {
    bimp_msg sendData;
    sendData.idCommand = 108;
    sendData.idUser = 0;
    sendData.idChat = 0;
    strcpy(sendData.data, "");
    if (isDebugActivated()) {
        printf("[ALIVE] User %d\n", idUser);
    }
    return sendData;
}

/* Traiter les demandes de changement de pseudonyme */
bimp_msg treatNick(bimp_msg receivedData, client *allClients) {
    bimp_msg sendData;
    if (isClientConnected(allClients, receivedData.idUser)) {
        int index = getUserIndexFromId(allClients, receivedData.idUser);
        strcpy(allClients[index].nickname, receivedData.data);
        sendData.idCommand = 109;
        sendData.idUser = 0;
        sendData.idChat = 0;
        strcpy(sendData.data, allClients[index].nickname);
        if (isDebugActivated()) {
            printf("[ACKNICK] User %d with username %s\n", receivedData.idUser, allClients[index].nickname);
        }
    } else {
        /* Créer la structure du ERROR */
        sendData.idCommand = 104;
        sendData.idUser = 0;
        sendData.idChat = 0;
        strcpy(sendData.data, "Client not connected");
        if (isDebugActivated()) {
            printf("[ERROR] Client not connected\n");
        }
    }
    return sendData;
}

/* Obtenir le pseudonyme d'un utilisateur depuis son ID */
bimp_msg treatGetNick(bimp_msg receivedData, client *allClients) {
    bimp_msg sendData;
    if (isClientConnected(allClients, receivedData.idUser)) {
        int index = getUserIndexFromId(allClients, receivedData.idUser);
        sendData.idCommand = 112;
        sendData.idUser = receivedData.idUser;
        sendData.idChat = 0;
        strcpy(sendData.data, allClients[index].nickname);
        if (isDebugActivated()) {
            printf("[ACKGETNICK] User %d has username %s\n", receivedData.idUser, allClients[index].nickname);
        }
    } else {
        /* Créer la structure du ERROR */
        sendData.idCommand = 104;
        sendData.idUser = 0;
        sendData.idChat = 0;
        strcpy(sendData.data, "Client not connected");
        if (isDebugActivated()) {
            printf("[ERROR] Client not connected\n");
        }
    }
    return sendData;
}